/* eslint-disable no-restricted-globals */
import { expect } from '@jest/globals';
import { scenario } from '@testduet/given-when-then';
import type { WebChatActivity } from '../../../types/WebChatActivity';
import type { LocalId } from './property/LocalId';
import { type Activity, type ActivityMapEntry, type SortedChatHistory } from './types';
import upsert, { INITIAL_STATE } from './upsert';

function activityToExpectation(activity: Activity, expectedPosition: number = expect.any(Number) as any): Activity {
  return {
    ...activity,
    channelData: {
      ...activity.channelData,
      'webchat:internal:position': expectedPosition
    } as any
  };
}

scenario('upserting 2 activities with timestamps', bdd => {
  const activity1: Activity = {
    channelData: {
      'webchat:internal:local-id': '_:a-00001' as LocalId,
      'webchat:internal:position': 0,
      'webchat:send-status': undefined
    },
    from: { id: 'bot', role: 'bot' },
    id: 'a-00001',
    text: 'Hello, World!',
    timestamp: new Date(1000).toISOString(),
    type: 'message'
  };

  const activity2: Activity = {
    channelData: {
      'webchat:internal:local-id': '_:a-00002' as LocalId,
      'webchat:internal:position': 0,
      'webchat:send-status': undefined
    },
    from: { id: 'bot', role: 'bot' },
    id: 'a-00002',
    text: 'Aloha!',
    timestamp: new Date(500).toISOString(),
    type: 'message'
  };

  bdd
    .given('an initial state', () => INITIAL_STATE)
    .when('upserted', state => upsert({ Date }, state, activity1))
    .then('`activityIdToLocalIdMap` should match', (_, state) => {
      expect(state.activityIdToLocalIdMap).toEqual(new Map<string, LocalId>([['a-00001', '_:a-00001' as LocalId]]));
    })
    .and('should have added activity to `activityMap`', (_, state) => {
      expect(state).toHaveProperty(
        'activityMap',
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: activityToExpectation(activity1),
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added activity to `sortedChatHistoryList`', (_, state) => {
      expect(state).toHaveProperty('sortedChatHistoryList', [
        {
          activityLocalId: '_:a-00001' as LocalId,
          logicalTimestamp: 1_000,
          type: 'activity'
        }
      ]);
    })
    .and('should match `sortedActivities` snapshot', (_, state) => {
      expect(state).toHaveProperty('sortedActivities', [activityToExpectation(activity1, 1_000)]);
    })
    .when('another activity is upserted', (_, state) => upsert({ Date }, state, activity2))
    .then('`activityIdToLocalIdMap` should match', (_, state) => {
      expect(state.activityIdToLocalIdMap).toEqual(
        new Map<string, LocalId>([
          ['a-00001', '_:a-00001' as LocalId],
          ['a-00002', '_:a-00002' as LocalId]
        ])
      );
    })
    .and('should have added activity to `activityMap`', (_, state) => {
      expect(state).toHaveProperty(
        'activityMap',
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: {
                channelData: {
                  'webchat:internal:local-id': '_:a-00001' as LocalId,
                  'webchat:internal:position': expect.any(Number),
                  'webchat:send-status': undefined
                } as any,
                from: { id: 'bot', role: 'bot' },
                id: 'a-00001',
                text: 'Hello, World!',
                timestamp: new Date(1_000).toISOString(),
                type: 'message'
              },
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00002' as LocalId,
            {
              activity: {
                channelData: {
                  'webchat:internal:local-id': '_:a-00002' as LocalId,
                  'webchat:internal:position': expect.any(Number),
                  'webchat:send-status': undefined
                } as any,
                from: { id: 'bot', role: 'bot' },
                id: 'a-00002',
                text: 'Aloha!',
                timestamp: new Date(500).toISOString(),
                type: 'message'
              },
              activityLocalId: '_:a-00002' as LocalId,
              logicalTimestamp: 500,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added activity to `sortedChatHistoryList`', (_, state) => {
      expect(state).toHaveProperty('sortedChatHistoryList', [
        {
          activityLocalId: '_:a-00002',
          logicalTimestamp: 500,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00001',
          logicalTimestamp: 1_000,
          type: 'activity'
        }
      ]);
    })
    .and('should match `sortedActivities` snapshot', (_, state) => {
      expect(state).toHaveProperty('sortedActivities', [
        activityToExpectation(activity2, 1),
        activityToExpectation(activity1, 1_000)
      ]);
    });
});

scenario('upserting activities which some with timestamp and some without', bdd => {
  const activity1: WebChatActivity = {
    channelData: {
      'webchat:internal:local-id': '_:a-00001' as LocalId,
      'webchat:internal:position': 0,
      'webchat:send-status': undefined
    },
    from: { id: 'bot', role: 'bot' },
    id: 'a-00001',
    text: 't=1000ms',
    timestamp: new Date(1_000).toISOString(),
    type: 'message'
  };

  const activity2: WebChatActivity = {
    channelData: {
      'webchat:internal:local-id': '_:a-00002' as LocalId,
      'webchat:internal:position': 0,
      'webchat:send-status': undefined
    },
    from: { id: 'bot', role: 'bot' },
    id: 'a-00002',
    text: 't=undefined',
    timestamp: undefined as any,
    type: 'message'
  };

  const activity3: WebChatActivity = {
    channelData: {
      'webchat:internal:local-id': '_:a-00003' as LocalId,
      'webchat:internal:position': 0,
      'webchat:send-status': undefined
    },
    from: { id: 'bot', role: 'bot' },
    id: 'a-00003',
    text: 't=2_000ms',
    timestamp: new Date(2_000).toISOString(),
    type: 'message'
  };

  const activity4: WebChatActivity = {
    channelData: {
      'webchat:internal:local-id': '_:a-00004' as LocalId,
      'webchat:internal:position': 0,
      'webchat:send-status': undefined
    },
    from: { id: 'bot', role: 'bot' },
    id: 'a-00004',
    text: 't=1500ms',
    timestamp: new Date(1_500).toISOString(),
    type: 'message'
  };

  const activity2b = { ...activity2, timestamp: new Date(1_750).toISOString() };

  bdd
    .given('an initial state', () => INITIAL_STATE)
    .when('upserting an activity with t=1000ms', state => upsert({ Date }, state, activity1))
    .then('should have added to `activityMap`', (_, state) => {
      expect(state.activityMap).toEqual(
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: activityToExpectation(activity1),
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added to `sortedChatHistoryList`', (_, state) => {
      expect(state.sortedChatHistoryList).toEqual([
        {
          activityLocalId: '_:a-00001' as LocalId,
          logicalTimestamp: 1_000,
          type: 'activity'
        }
      ] satisfies SortedChatHistory);
    })
    .and('`sortedActivities` should match', (_, state) => {
      expect(state.sortedActivities).toEqual([activityToExpectation(activity1, 1_000)]);
    })
    .when('upserting an activity with t=undefined', (_, state) => upsert({ Date }, state, activity2))
    .then('should have added to `activityMap`', (_, state) => {
      expect(state.activityMap).toEqual(
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: activityToExpectation(activity1),
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00002' as LocalId,
            {
              activity: activityToExpectation(activity2),
              activityLocalId: '_:a-00002' as LocalId,
              logicalTimestamp: undefined,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added to `sortedChatHistoryList`', (_, state) => {
      expect(state.sortedChatHistoryList).toEqual([
        {
          activityLocalId: '_:a-00001' as LocalId,
          logicalTimestamp: 1_000,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00002' as LocalId,
          logicalTimestamp: undefined,
          type: 'activity'
        }
      ] satisfies SortedChatHistory);
    })
    .and('`sortedActivities` should match', (_, state) => {
      expect(state.sortedActivities).toEqual([
        activityToExpectation(activity1, 1_000),
        activityToExpectation(activity2, 2_000)
      ]);
    })
    .when('upserting an activity with t=2_000ms', (_, state) => upsert({ Date }, state, activity3))
    .then('should have added to `activityMap`', (_, state) => {
      expect(state.activityMap).toEqual(
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: activityToExpectation(activity1),
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00002' as LocalId,
            {
              activity: activityToExpectation(activity2),
              activityLocalId: '_:a-00002' as LocalId,
              logicalTimestamp: undefined,
              type: 'activity'
            }
          ],
          [
            '_:a-00003' as LocalId,
            {
              activity: activityToExpectation(activity3),
              activityLocalId: '_:a-00003' as LocalId,
              logicalTimestamp: 2_000,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added to `sortedChatHistoryList`', (_, state) => {
      expect(state.sortedChatHistoryList).toEqual([
        {
          activityLocalId: '_:a-00001' as LocalId,
          logicalTimestamp: 1_000,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00002' as LocalId,
          logicalTimestamp: undefined,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00003' as LocalId,
          logicalTimestamp: 2_000,
          type: 'activity'
        }
      ] satisfies SortedChatHistory);
    })
    .and('`sortedActivities` should match', (_, state) => {
      expect(state.sortedActivities).toEqual([
        activityToExpectation(activity1, 1_000),
        activityToExpectation(activity2, 2_000),
        activityToExpectation(activity3, 3_000)
      ]);
    })
    .when('upserting an activity with t=1500ms', (_, state) => upsert({ Date }, state, activity4))
    .then('should have added to `activityMap`', (_, state) => {
      expect(state.activityMap).toEqual(
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: activityToExpectation(activity1),
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00002' as LocalId,
            {
              activity: activityToExpectation(activity2),
              activityLocalId: '_:a-00002' as LocalId,
              logicalTimestamp: undefined,
              type: 'activity'
            }
          ],
          [
            '_:a-00003' as LocalId,
            {
              activity: activityToExpectation(activity3),
              activityLocalId: '_:a-00003' as LocalId,
              logicalTimestamp: 2_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00004' as LocalId,
            {
              activity: activityToExpectation(activity4),
              activityLocalId: '_:a-00004' as LocalId,
              logicalTimestamp: 1_500,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added to `sortedChatHistoryList`', (_, state) => {
      expect(state.sortedChatHistoryList).toEqual([
        {
          activityLocalId: '_:a-00001' as LocalId,
          logicalTimestamp: 1_000,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00002' as LocalId,
          logicalTimestamp: undefined,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00004' as LocalId,
          logicalTimestamp: 1_500,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00003' as LocalId,
          logicalTimestamp: 2_000,
          type: 'activity'
        }
      ] satisfies SortedChatHistory);
    })
    .and('`sortedActivities` should match', (_, state) => {
      expect(state.sortedActivities).toEqual([
        activityToExpectation(activity1, 1_000),
        activityToExpectation(activity2, 2_000),
        activityToExpectation(activity4, 2_001),
        activityToExpectation(activity3, 3_000)
      ]);
    })
    .when('upserting the t=undefined activity is updated with t=1750ms', (_, state) =>
      upsert({ Date }, state, activity2b)
    )
    .then('should have added to `activityMap`', (_, state) => {
      expect(state.activityMap).toEqual(
        new Map<LocalId, ActivityMapEntry>([
          [
            '_:a-00001' as LocalId,
            {
              activity: activityToExpectation(activity1),
              activityLocalId: '_:a-00001' as LocalId,
              logicalTimestamp: 1_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00002' as LocalId,
            {
              activity: activityToExpectation(activity2b),
              activityLocalId: '_:a-00002' as LocalId,
              logicalTimestamp: 1_750,
              type: 'activity'
            }
          ],
          [
            '_:a-00003' as LocalId,
            {
              activity: activityToExpectation(activity3),
              activityLocalId: '_:a-00003' as LocalId,
              logicalTimestamp: 2_000,
              type: 'activity'
            }
          ],
          [
            '_:a-00004' as LocalId,
            {
              activity: activityToExpectation(activity4),
              activityLocalId: '_:a-00004' as LocalId,
              logicalTimestamp: 1_500,
              type: 'activity'
            }
          ]
        ])
      );
    })
    .and('should have added to `sortedChatHistoryList`', (_, state) => {
      expect(state.sortedChatHistoryList).toEqual([
        {
          activityLocalId: '_:a-00001' as LocalId,
          logicalTimestamp: 1_000,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00004' as LocalId,
          logicalTimestamp: 1_500,
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00002' as LocalId,
          logicalTimestamp: 1_750, // Update activity is moved here.
          type: 'activity'
        },
        {
          activityLocalId: '_:a-00003' as LocalId,
          logicalTimestamp: 2_000,
          type: 'activity'
        }
      ] satisfies SortedChatHistory);
    })
    .and('`sortedActivities` should match', (_, state) => {
      expect(state.sortedActivities).toEqual([
        activityToExpectation(activity1, 1_000),
        activityToExpectation(activity4, 2_001),
        activityToExpectation(activity2b, 2_002),
        activityToExpectation(activity3, 3_000)
      ]);
    });
});
